package com.css.sword.knowing.search.tool;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesRequest;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
import org.elasticsearch.action.admin.indices.forcemerge.ForceMergeResponse;
import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
import org.elasticsearch.action.admin.indices.get.GetIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsRequestBuilder;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Requests;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

import com.css.sword.knowing.search.Searcher;

/**
 * 索引工具类，操作索引
 * 
 * @author 作者 E-mail:caihx@css.com.cn
 * @version 创建时间：2016年11月1日 上午9:23:01 类说明
 */
public class IndexTool {

	/**
	 * 判断一个索引在服务器上是否存在
	 * 
	 * @param indexName
	 *            索引名称
	 * @return 存在返回true，不存在返回false
	 */
	public static boolean indexExists(String indexName) {
		return indexExists(null, indexName);
	}
	
	public static boolean indexExists(TransportClient client, String indexName) {

		if(null == client)
		    client = Searcher.getTransportClient();

		IndicesExistsRequest extRequest = Requests.indicesExistsRequest(indexName);
		IndicesExistsResponse extResponse = client.admin().indices().exists(extRequest).actionGet();
		if (extResponse != null && extResponse.isExists())
			return true;

		return false;
	}

	/**
	 * 判断多个索引是否存在，遇到第一个不存在的索引，则不再检测
	 * 
	 * @param indexNames
	 *            索引名称数组
	 * @return 被检测索引全部存在返回空字符串，有不存在的索引返回第一个不存在索引名称
	 */
	public static String indexExists(String[] indexNames) {

		return indexExists(null, indexNames);
	}
	
	public static String indexExists(TransportClient client, String[] indexNames) {

		for (int i = 0; i < indexNames.length; i++) {
			if (!IndexTool.indexExists(client, indexNames[i])) {
				return indexNames[i];
			}
		}
		return "";
	}

	/**
	 * 获取服务器所有索引的名称
	 * 
	 * @return 索引名称数组
	 */
	public static String[] getIndexs() {

		return getIndexs(null);
	}
	
	public static String[] getIndexs(TransportClient client) {

		if(null == client)
			client = Searcher.getTransportClient();
		
		GetIndexRequest req = new GetIndexRequest();

		try {
			GetIndexResponse res = client.admin().indices().getIndex(req).actionGet();
			return res.getIndices();
		} catch (Exception e) {
			return null;
		}
	}

	private static String putIndex(TransportClient client, SearchHits hits, String indexName) {

		IndexRequestBuilder irb = null;
		BulkRequestBuilder bulkRequest = client.prepareBulk();
		for (SearchHit hit : hits) {
			irb = client.prepareIndex(indexName, hit.getType());
			irb.setSource(hit.getSource());
			bulkRequest.add(irb);

			irb.setId(hit.getId());
		}
		bulkRequest.setRefresh(true);

		BulkResponse bulkResponse = bulkRequest.execute().actionGet();
		if (bulkResponse.hasFailures()) {
			return bulkResponse.buildFailureMessage();
		}

		return "";
	}

	/**
	 * 重建索引，前提是保证服务器上的索引存储了原始数据
	 * 
	 * @param oldIndexName
	 *            待重建的索引名称
	 * @param newIndexName
	 *            新的索引名称
	 * @param createMapping
	 *            是否使用原mapping结构，ture：使用，false:使用
	 * @param size
	 *            多少条提交一次
	 * @return 创建成功返回空字符串，创建失败返回错误信息
	 */
	public static String rebuild(String oldIndexName, String newIndexName, boolean createMapping, long size) {
		return rebuild(null, oldIndexName, newIndexName, null, createMapping, size);
	}
	
	public static String rebuild(TransportClient client, String oldIndexName, String newIndexName, boolean createMapping, long size) {
		return rebuild(client, oldIndexName, newIndexName, null, createMapping, size);
	}

	/**
	 * 重建索引，前提是保证服务器上的索引存储了原始数据
	 * 
	 * @param oldIndexName
	 *            待重建的索引名称
	 * @param newIndexName
	 *            新的索引名称
	 * @param typeNames
	 *            索引类型名称数组
	 * @param createMapping
	 *            是否使用原mapping结构，ture：使用，false:使用
	 * @param size
	 *            多少条提交一次
	 * @return 创建成功返回空字符串，创建失败返回错误信息
	 */
	
	public static String rebuild(String oldIndexName, String newIndexName, String[] typeNames, boolean createMapping,
			long size) {
		return rebuild(null, oldIndexName, newIndexName, typeNames, createMapping, size);
	}
	
	public static String rebuild(TransportClient client, String oldIndexName, String newIndexName, String[] typeNames, boolean createMapping,
			long size) {
		String errMsg = "";
		long totalHits = 0;
		long length = 0;

		if(null == client)
			client = Searcher.getTransportClient();
		
		if (createMapping) {
			Map<String, Map<String, Object>> mappings = getMappings(oldIndexName);
			if (null != mappings) {

				if (typeNames != null || typeNames.length > 0) {
					for (int i = 0; i < typeNames.length; i++) {
						Map<String, Object> mapping = mappings.get(typeNames[i]);
						if (mapping != null) {
							MappingTool.create(client, newIndexName, typeNames[i], mapping, null);
						}
					}
				} else {
					Iterator iter = mappings.entrySet().iterator();
					while (iter.hasNext()) {
						Entry entry = (Entry) iter.next();
						Map<String, Object> mapping = (Map<String, Object>) entry.getValue();
						MappingTool.create(client, newIndexName, (String) entry.getKey(), mapping, null);
					}
				}
			}
		}

		SearchRequestBuilder srb = client.prepareSearch(oldIndexName).setQuery(QueryBuilders.matchAllQuery())
				.setSize((int) size).setScroll(new TimeValue(600000));
		if (null != typeNames && typeNames.length > 0)
			srb.setTypes(typeNames);

		SearchResponse sr = srb.execute().actionGet();

		totalHits = sr.getHits().getTotalHits();
		length = sr.getHits().hits().length;

		String scrollId = sr.getScrollId();
		long index = length;
		errMsg = putIndex(client, sr.getHits(), newIndexName);
		System.out.println("total:" + totalHits + ",complate:" + size);
		if (!errMsg.isEmpty())
			return errMsg;

		while (length == size) {
			SearchResponse ssr = client.prepareSearchScroll(scrollId).setScroll(new TimeValue(600000)).execute()
					.actionGet();

			length = ssr.getHits().hits().length;

			if (length <= 0)
				break;

			index += size;
			errMsg = putIndex(client, ssr.getHits(), newIndexName);
			if (!errMsg.isEmpty())
				return errMsg;

			System.out.println("total:" + totalHits + ",complate:" + index);
		}
		if (length > 0)
			System.out.println("total:" + totalHits + ",complate:" + index);

		return errMsg;
	}

	/**
	 * 创建一个只有属性的空索引，没有具体数据
	 * 
	 * @param indexName
	 *            索引名称
	 * @param settings
	 *            索引的属性设置
	 * @return true：创建成功，false：创建失败
	 */
	public static boolean create(String indexName, Map<String, Object> settings) {
		return create(null, indexName, settings);
	}
	
	public static boolean create(TransportClient client, String indexName, Map<String, Object> settings) {
		boolean ret = false;

		if(null == client)
			client = Searcher.getTransportClient();

		if (indexName.isEmpty())
			return ret;

		CreateIndexResponse res = client.admin().indices().prepareCreate(indexName).setSettings(settings).execute()
				.actionGet();
		if (res != null)
			ret = res.isAcknowledged();

		return ret;
	}

	/**
	 * 删除一个索引
	 * 
	 * @param indexName
	 *            索引名称
	 * @return true：删除成功，false：删除失败
	 */
	public static boolean delete(String indexName) {
		return delete(null, indexName);
	}
	
	public static boolean delete(TransportClient client, String indexName) {
		boolean ret = false;

		if (indexName.isEmpty())
			return ret;

		if(null == client)
			client = Searcher.getTransportClient();
		
		if (indexExists(client, indexName)) {
			DeleteIndexRequest req = Requests.deleteIndexRequest(indexName);
			DeleteIndexResponse res = client.admin().indices().delete(req).actionGet();
			if (res != null)
				ret = res.isAcknowledged();
		}

		return ret;
	}

	/**
	 * 通过检索条件删除索引
	 * 
	 * @param indexName
	 *            索引名称
	 * @param typeNames
	 *            索引类型
	 * @param fieldName
	 *            字段名称，作为条件的key
	 * @param fieldValue
	 *            字段值， 作为条件的value
	 * @return true：删除成功，false：删除失败
	 */
	public static boolean deleteByQuery(String indexName, String[] typeNames, String fieldName, String fieldValue) {
		return deleteByQuery(null, indexName, typeNames, fieldName, fieldValue);
	}
	
	public static boolean deleteByQuery(TransportClient client, String indexName, String[] typeNames, String fieldName, String fieldValue) {
		boolean ret = false;

		if(null == client)
			client = Searcher.getTransportClient();
		if (indexName.isEmpty())
			return ret;

		QueryBuilder query = QueryBuilders.termQuery(fieldName, fieldValue);
//		DeleteByQueryResponse res = client.prepareDeleteByQuery(indexName).setTypes(typeNames).setQuery(query).execute()
//				.actionGet();

//		return res.isContextEmpty();
		return false;
	}

	/**
	 * 清空索引，保留索引setting和mapping信息
	 * 
	 * @param indexName
	 *            索引名称
	 * @return true：成功，false：失败
	 */
	public static boolean clean(String indexName) {
		return clean(null, indexName);
	}
	
	public static boolean clean(TransportClient client, String indexName) {
		boolean ret = false;

		if (indexName.isEmpty())
			return ret;
		
		if(null == client)
			client = Searcher.getTransportClient();

		delete(client, indexName);
		QueryBuilder query = QueryBuilders.matchAllQuery();
//		DeleteByQueryResponse res = client.prepareDeleteByQuery(indexName).setQuery(query).execute().actionGet();

//		return res.isContextEmpty();
		
		return false;
	}

	/**
	 * 索引优化，此操作比较耗费系统资源。请选择在空闲时优化
	 * 
	 * @param indexName
	 *            索引名称
	 * @param maxNumSegments
	 *            合并后最大的分段数目
	 * @param waitForMerge
	 *            是否锁定索引，等待优化完成才对外服务。一般选择false，不锁定
	 * @return true：成功，false：失败
	 */
	public static boolean mergeSegment(String indexName, long maxNumSegments, boolean waitForMerge) {
		
		return mergeSegment(null, indexName, maxNumSegments, waitForMerge);
	}
	
	public static boolean mergeSegment(TransportClient client, String indexName, long maxNumSegments, boolean waitForMerge) {
		boolean ret = false;

		if (indexName.isEmpty())
			return ret;

		if(null == client)
			client = Searcher.getTransportClient();
		
		if (indexExists(client, indexName)) {
			ForceMergeResponse res = client.admin().indices().prepareForceMerge(indexName)
					.setMaxNumSegments((int) maxNumSegments).execute().actionGet();
			if (res != null)
				ret = res.isContextEmpty();
		}

		return ret;
	}

	private static boolean validateValue(Object obj) {
		if (null == obj)
			return false;

		if (obj.getClass().getSimpleName().equals("String[]")) {
			String[] temp = (String[]) obj;
			if (temp.length == 0)
				return false;

			for (int i = 0; i < temp.length; i++) {
				if (null == temp[i] || temp[i].isEmpty())
					return false;
			}
		} else if (obj.getClass().getSimpleName().equals("String")) {
			String temp = (String) obj;
			if (temp.isEmpty())
				return false;
		}

		return true;
	}

	/**
	 * 获取服务器上所有的索引别名，以及对应的索引名称
	 * 
	 * @return 每个索引对应的别名列表
	 */
	public static Map<String, List<String>> getAliaseMapIndexes() {
		return getAliaseMapIndexes(null);
	}
	public static Map<String, List<String>> getAliaseMapIndexes(TransportClient client) {
		Map<String, List<String>> aliases = new HashMap<String, List<String>>();


		if(null == client)
			client = Searcher.getTransportClient();

		GetAliasesRequest req = new GetAliasesRequest();
		// req.indices(indexNames);

		GetAliasesResponse res = client.admin().indices().getAliases(req).actionGet();
		if (null != res) {
			ImmutableOpenMap<String, List<AliasMetaData>> aliasesMap = res.getAliases();
			for (com.carrotsearch.hppc.cursors.ObjectCursor<String> key : aliasesMap.keys()) {
				List<AliasMetaData> amds = aliasesMap.get(key.value);
				if (null != amds) {
					ArrayList<String> al = null;
					for (AliasMetaData a : amds) {

						if (aliases.containsKey(a.getAlias())) {
							al = (ArrayList<String>) aliases.get(a.getAlias());
						} else {
							al = new ArrayList<String>();
							aliases.put(a.getAlias(), al);
						}

						if (!listContainKey(al, key.value)) {
							al.add(key.value);
						}
					}
				}
			}
		}

		return aliases;
	}

	/**
	 * 获取服务器上所有的索引，以及对应的索引别名
	 * 
	 * @return 每个索引对应的别名列表
	 */
	public static Map<String, List<String>> getIndexMapAliases() {
		return getIndexMapAliases(null);
	}
	
	public static Map<String, List<String>> getIndexMapAliases(TransportClient client) {
		Map<String, List<String>> indexes = new HashMap<String, List<String>>();

		if(null == client)
			client = Searcher.getTransportClient();
		
		GetAliasesRequest req = new GetAliasesRequest();
		// req.indices(indexNames);

		GetAliasesResponse res = client.admin().indices().getAliases(req).actionGet();
		if (null != res) {
			ImmutableOpenMap<String, List<AliasMetaData>> aliasesMap = res.getAliases();
			for (com.carrotsearch.hppc.cursors.ObjectCursor<String> key : aliasesMap.keys()) {
				List<AliasMetaData> amds = aliasesMap.get(key.value);
				if (null != amds) {
					ArrayList<String> al = new ArrayList<String>();
					for (AliasMetaData a : amds) {
						indexes.put(key.value, al);
						al.add(a.getAlias());
					}
					indexes.put(key.value, al);
				}
			}
		}

		return indexes;
	}

	/**
	 * 为索引创建或删除别名
	 * 
	 * @param removeIndexNames
	 *            待删除别名的索引名称
	 * @param addIndexNames
	 *            待添加别名的索引名称
	 * @param aliasNames
	 *            别名名称
	 * @return true：成功，false：失败
	 */
	public static boolean aliasOpt(String[] removeIndexNames, String[] addIndexNames, String[] aliasNames) {
		
		return aliasOpt(null, removeIndexNames, addIndexNames, aliasNames);
	}
	
	public static boolean aliasOpt(TransportClient client, String[] removeIndexNames, String[] addIndexNames, String[] aliasNames) {
		boolean ret = false;

		if (!validateValue(aliasNames))
			return ret;

		if(null == client)
			client = Searcher.getTransportClient();

		IndicesAliasesRequest req = new IndicesAliasesRequest();

		if (validateValue(removeIndexNames))
			if (IndexTool.indexExists(client, removeIndexNames).isEmpty())
				req.removeAlias(removeIndexNames, aliasNames);

		if (validateValue(addIndexNames)) {
			if (IndexTool.indexExists(client, addIndexNames).isEmpty()) {
				for(int i=0; i<aliasNames.length; i++)
					req.addAlias(aliasNames[i], addIndexNames);

				IndicesAliasesResponse res = client.admin().indices().aliases(req).actionGet();
				if (res != null)
					ret = res.isContextEmpty();
			}
		}

		return ret;
	}

	private static boolean listContainKey(List<String> list, String key) {
		if (null == list || list.size() == 0)
			return false;

		for (String val : list) {
			if (val.equalsIgnoreCase(key))
				return true;
		}

		return false;
	}

	/**
	 * 获取索引下某个type的mapping
	 * 
	 * @param indexName
	 *            索引名称
	 * @param typeName
	 *            类型名称
	 * @return 返回Map类型的mapping结构
	 */
	public static Map<String, Object> getMapping(String indexName, String typeName) {

		return getMapping(null, indexName, typeName);
	}
	
	public static Map<String, Object> getMapping(TransportClient client, String indexName, String typeName) {

		if(null == client)
			client = Searcher.getTransportClient();

		GetMappingsRequestBuilder req = client.admin().indices().prepareGetMappings(indexName);
		GetMappingsResponse res = req.execute().actionGet();

		ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mps = res.getMappings();

		if (null != mps) {
			ImmutableOpenMap<String, MappingMetaData> mpDatas = mps.get(indexName);
			if (null != mpDatas) {
				MappingMetaData mpData = mpDatas.get(typeName);
				if (null == mpData)
					return null;

				try {
					return mpData.getSourceAsMap();
				} catch (IOException e) {
					// e.printStackTrace();
				}
			}
		}
		return null;
	}

	/**
	 * 获取索引下所有type的mapping
	 * 
	 * @param indexName
	 *            索引名称
	 * @return 返回Map<String, Map<String, Object>>类型的所有mapping结构
	 */
	public static Map<String, Map<String, Object>> getMappings(String indexName) {

		return getMappings(indexName);
	}
	
	public static Map<String, Map<String, Object>> getMappings(TransportClient client, String indexName) {

		Map<String, Map<String, Object>> mappings = new HashMap<String, Map<String, Object>>();

		if(null == client)
			client = Searcher.getTransportClient();

		GetMappingsRequestBuilder req = client.admin().indices().prepareGetMappings(indexName);
		GetMappingsResponse res = req.execute().actionGet();

		ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mps = res.getMappings();

		if (null != mps) {
			ImmutableOpenMap<String, MappingMetaData> mpDatas = mps.get(indexName);

			com.carrotsearch.hppc.ObjectLookupContainer<String> keys = mpDatas.keys();
			for (com.carrotsearch.hppc.cursors.ObjectCursor<String> key : keys) {
				MappingMetaData data = mpDatas.get(key.value);

				try {
					mappings.put(key.value, data.getSourceAsMap());
				} catch (IOException e) {
					// e.printStackTrace();
				}
			}
		}
		return mappings;
	}
	

	public static void main(String[] args) {

		// boolean ret = mergeSegment("artistnews", 1, false);
		// System.out.println(ret);
		// deleteIndexByName("artistnews");
		// createArtistnews();
		// dropMapping("t1", new String[]{});
		// getMappings("artistnews");
		// getIndexs();
		rebuild("artistnews", "artistnews_v1", true, 500);
		// t();
	}
}
